#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "nvs_storage_hal.h"

static const char *TAG = "nvs_storage_hal";

static SemaphoreHandle_t xNVSSemaphore; /* nvs mutex */
static nvs_handle_t nvs_handle_user;

/**
 * @brief nvs_init
 *
 */
void nvs_init(void)
{
    /* nvs mutex create start */
    xNVSSemaphore = xSemaphoreCreateMutex(); /* create ch423 mutex */
    if (xNVSSemaphore != NULL)
    {
        ESP_LOGI(TAG, "Creat xNVSSemaphore successfully");
    }
    else
    {
        /* error handler begin */
        ESP_LOGE(TAG, "Creat xNVSSemaphore error. err msg->%s,,%s", __FILE__, __func__);
        vSemaphoreDelete(xNVSSemaphore);
        /* error handler end */
    }
    /* nvs mutex create end */

    // Initialize NVS
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        // NVS partition was truncated and needs to be erased
        // Retry nvs_flash_init
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);
}

/**
 * @brief nvs_flash_get
 *
 * @param namespace_name
 * @param key
 * @return int32_t
 */
int32_t nvs_flash_get(const char *namespace_name, const char *key)
{
    esp_err_t err;
    int32_t nvs_get_buff = 0; // value will default to 0, if not set yet in NVS

    if (pdTRUE == xSemaphoreTake(xNVSSemaphore, portMAX_DELAY))
    {
        // Open
        printf("\n");
        printf("Opening Non-Volatile %s (NVS) handle... ", namespace_name);

        err = nvs_open(namespace_name, NVS_READWRITE, &nvs_handle_user);
        if (err != ESP_OK)
        {
            printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
        }
        else
        {
            printf("Done\n");

            // Read
            printf("Reading %s from NVS ... ", key);
            err = nvs_get_i32(nvs_handle_user, key, &nvs_get_buff);
            switch (err)
            {
            case ESP_OK:
                printf("Done\n");
                printf("%s = %" PRIu32 "\n", key, nvs_get_buff);
                break;
            case ESP_ERR_NVS_NOT_FOUND:
                printf("The value is not initialized yet!\n");
                break;
            default:
                printf("Error (%s) reading!\n", esp_err_to_name(err));
            }

            /*Commit written value.
            After setting any values, nvs_commit() must be called to ensure changes are written
            to flash storage. Implementations may write to storage at other times,
            but this is not guaranteed.*/
            printf("Committing updates in NVS ... ");
            err = nvs_commit(nvs_handle_user);
            printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
            // Close
            nvs_close(nvs_handle_user);
        }

        xSemaphoreGive(xNVSSemaphore); /* release nvs mutex */
    }
    return nvs_get_buff;
}

/**
 * @brief nvs_flash_set
 *
 * @param namespace_name
 * @param key
 * @param input_data
 */
void nvs_flash_set(const char *namespace_name, const char *key, int32_t input_data)
{
    esp_err_t err;

    if (pdTRUE == xSemaphoreTake(xNVSSemaphore, portMAX_DELAY))
    {
        // Open
        printf("Opening Non-Volatile %s (NVS) handle... ", namespace_name);
        // nvs_handle_t nvs_handle_user;
        err = nvs_open(namespace_name, NVS_READWRITE, &nvs_handle_user);
        if (err != ESP_OK)
        {
            printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
        }
        else
        {
            printf("Done\n");
            // Write
            printf("Updating %s in NVS ... ", key);
            err = nvs_set_i32(nvs_handle_user, key, input_data);
            printf((err != ESP_OK) ? "Failed!\n" : "Done\n");

            /*Commit written value.
            After setting any values, nvs_commit() must be called to ensure changes are written
            to flash storage. Implementations may write to storage at other times,
            but this is not guaranteed.*/
            printf("Committing updates in NVS ... ");
            err = nvs_commit(nvs_handle_user);
            printf((err != ESP_OK) ? "Failed!\n" : "Done\n");

            // Close
            nvs_close(nvs_handle_user);
        }

        xSemaphoreGive(xNVSSemaphore); /* release nvs mutex */
    }
}
